
package ClientGraphics;


import GameEngine.GameConstants;
import GameEngine.GameConstants.Levels;
import GameEngine.Vector;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import Objects.BasicObject;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.io.File;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JPanel;

/**
 * This is the main class which should be instantiated for the purpose of rendering the game graphics.
 * Calls to the methods of this class should be controlled by a networking class that is connected to the
 * socket and has a GUI itself, of which the display panel created by this class is a jpanel.
 * @author mhfowler
 */
public class GraphicsMain {

    final int TIMER_INTERVAL = 25;
    public final int WINDOW_SIZE_X = 275;
    public final int WINDOW_SIZE_Y;
    public final double WIDESCREEN_RATIO = 1.1;
    public BufferedImage waitScreen;


    // the id of the object this graphics is associated with (eg a particular player)
    short UID;

    /* Panel where the graphics will be display */
    private JPanel graphicsPanel;

    /* this thread calls render every TIMER_INTERVAL */
    RenderThread renderer;

    /* this is a screen buffer, to prevent flickering during animation */
    GraphicObject background;
    BufferedImage screenBuffer;

    /* information about level which should be specified at construction */
    private Levels levelName;
    private Dimension paneSize;
    private int levelW, levelH;

    /* this is a hashmap associating all objects in a basic level with all correspondent graphic objects */
    HashMap<Short,GraphicObject> hash;
    /* this is a list of all graphic objects that need to be rendered, this should be sorted based on the layer */
    /* this can include graphic objects which are purely part of animation and are not represented in the gamestate */
    List<GraphicObject> toRender;


    /* constructor */
    public GraphicsMain(Levels name, Dimension size, int lW, int lH)
    {
        levelName = name;
        System.out.println("levelname: " + levelName);
        paneSize = size;
        constructGraphicsPanel(paneSize.width, paneSize.height);
        hash = new HashMap<Short,GraphicObject>();
        toRender = new LinkedList<GraphicObject>();
        renderer = new RenderThread(TIMER_INTERVAL, this);
        UID = -1;

        levelW = lW;
        levelH = lH;

        WINDOW_SIZE_Y = (int) Math.floor(((double) WINDOW_SIZE_X*WIDESCREEN_RATIO)*790.0/1024.0);
    }


    public int getLevelH() {
        return levelH;
    }

    public void setLevelH(int levelH) {
        this.levelH = levelH;
        if (screenBuffer != null) {
            screenBuffer = new BufferedImage(levelW, levelH, BufferedImage.TYPE_INT_ARGB);
        }
    }

    public int getLevelW() {
        return levelW;
    }

    public void setLevelW(int levelW) {
        this.levelW = levelW;
        if (screenBuffer != null) {
            screenBuffer = new BufferedImage(levelW, levelH, BufferedImage.TYPE_INT_ARGB);
        }
    }


    /* resize level */
    /* changes level size and rescales background */
    public void resize(Dimension dim)
    {
        graphicsPanel.setSize(dim);
        renderer.stopTimer();
        renderer = new RenderThread(TIMER_INTERVAL, this);
        paneSize = dim;
        this.initialize();
    }



    /* this method should be called after construction, to initialize the background and start rendering */
    public void initialize()
    {
        background = LoadGraphic.loadBackground(levelName, new Dimension(levelW, levelH));
        LoadGraphic.loadAnimation(background);
        screenBuffer = new BufferedImage(levelW, levelH, BufferedImage.TYPE_INT_ARGB);
        resetScreenBuffer();
        renderer.start();
    }




    public void drawWaitScreen()
    {

        try
        {

            waitScreen = ImageIO.read(new File("Images/Menus/clientWait.jpg"));
            System.out.println(graphicsPanel);
            graphicsPanel.getGraphics().drawImage(waitScreen, 0, 0, null);
            System.out.println("Printing wait screen");
        }
        catch (IOException ex)
        {

        }

    }

    /* this method resets the screen buffer to the level background */
    public void resetScreenBuffer() {
        Graphics2D g = screenBuffer.createGraphics();
        drawObject(g, background);
    }


    /* this method should take in the most recent list of changed objects from the game server, and change the values
     * of the specific objects in this list, including loading the appropriate image for each object */
    public synchronized void update(List<BasicObject> l) {
        List<GraphicObject> toAdd;
        Iterator<BasicObject> iter = l.iterator();
        GraphicObject tempGraph;
        BasicObject tempBasic;
        short id;
        while (iter.hasNext()) {
            tempBasic = iter.next();
            id = tempBasic.getId();
             // check if it already exists, if so get it
            if (hash.containsKey(id)) {
                tempGraph = hash.get(id);
            }
            // else we must create new graphic object and put in hashtable as well as toRender list
            else {
                tempGraph = new GraphicObject(); 
                hash.put(id, tempGraph);
                toRender.add(tempGraph);
            }
            //update values
            tempGraph.update(tempBasic);
            // now check if it does not exist, which if true we must remove it
            if (!tempGraph.exists) {
                hash.remove(id);
                toRender.remove(tempGraph);
            }
        }
        // now we must update the lifetime of all graphic objects
        Set<Short> keys = hash.keySet();
        Iterator<Short> hashIter = keys.iterator();
        while (hashIter.hasNext()) {
            short i = hashIter.next();
            tempGraph = hash.get(i);
            // check if its graphic is the same as last round
            if (tempGraph.current == tempGraph.former) {
                tempGraph.lifetime = tempGraph.lifetime + 1;
            }
            // else reset and load animation
            else {
                tempGraph.lifetime = 0;
                LoadGraphic.loadAnimation(tempGraph);
            }  
        }
    }

    /* this method should first sort the toRender list based on layer,
     and then iterate through all objects in toRenderand draw them */
    public synchronized void render() {
        Collections.sort(toRender);
        Iterator<GraphicObject> iter = toRender.iterator();
        Graphics bufferGraphics = screenBuffer.getGraphics();
        Graphics2D g = (Graphics2D) bufferGraphics;
        GraphicObject temp;
        while (iter.hasNext()) {
            temp = iter.next();
            drawObject(g, temp);
        }
        // create buffer
        Graphics panelGraphics = graphicsPanel.getGraphics();
        Graphics2D r = (Graphics2D) panelGraphics;
        // check if there is an associated player for this graphics, if not draw whole level
        if (UID == -1) {    // -1 signifies not set
            r.drawImage(screenBuffer, null, 0, 0);
        }
        // else if there is, then just draw relevant part of level
        else {
            GraphicObject player = hash.get(UID);
            // check if player has been instantiated yet
            
            if (player!=null) {
                int sCorner1x = Math.max(0,((int) player.minPos.getX() - WINDOW_SIZE_X));
                int sCorner1y = Math.max(0,((int) player.minPos.getY() - WINDOW_SIZE_Y));
                int sCorner2x = (int) (sCorner1x + 2 * WINDOW_SIZE_X + player.size.getX());
                int sCorner2y = (int) (sCorner1y + 2 * WINDOW_SIZE_Y + player.size.getY());

                // check if bottom right corner is off screen
                
                if (sCorner2x > levelW) {
                    sCorner2x = levelW;
                    sCorner1x = (int) (sCorner2x - 2 * WINDOW_SIZE_X - player.size.getX());
                }
                if (sCorner2y > levelH) {
                    sCorner2y = levelH;
                    sCorner1y = (int) (sCorner2y - 2 * WINDOW_SIZE_Y - player.size.getY());
                }
                
                // now render correct portions of image
                  
                  
                boolean test = r.drawImage(screenBuffer, 0, 0, graphicsPanel.getWidth(), graphicsPanel.getHeight(), sCorner1x, sCorner1y, sCorner2x, sCorner2y, Color.DARK_GRAY, null);
            }
            // else just render whole thing
            else {
                r.drawImage(screenBuffer, null, 0, 0);
            }
        }
        resetScreenBuffer();

    }



    /* this method should cause the render thread to start rendering again */
    public void startRendering()
    {
        renderer.reStartTimer();
    }

    /* this method should cause the the render thread to stop rendering */
    public void stopRendering() {
        renderer.stopTimer();
    }





    /* takes in the dimensions that the game graphics will be display in and creates a jpanel
     * of that size */
    public void constructGraphicsPanel(float x, float y) {
        graphicsPanel = new JPanel();
        graphicsPanel.setPreferredSize(new Dimension((int)x,(int)y));
    }



    // getters and setters
        public JPanel getGraphicsPanel() {
        return graphicsPanel;
    }

    public void setGraphicsPanel(JPanel graphicsPanel) {
        this.graphicsPanel = graphicsPanel;
    }

    public HashMap<Short, GraphicObject> getHash() {
        return hash;
    }

    public void setHash(HashMap<Short, GraphicObject> hash) {
        this.hash = hash;
    }

    public Levels getLevelName() {
        return levelName;
    }

    public void setLevelName(Levels levelName) {
        this.levelName = levelName;
    }

    public Dimension getLevelSize() {
        return paneSize;
    }

    public void setLevelSize(Dimension levelSize) {
        this.paneSize = levelSize;
    }



    public void drawObject(Graphics2D g, GraphicObject o) {
        try {
            ImageWrapper wrapper = o.getImage();
            Vector pos = o.minPos;
            int posx = (int) pos.getX() + wrapper.xOffset;
            int posy = (int) pos.getY() + wrapper.yOffset;
            g.drawImage(wrapper.img, null, posx, posy);
        } catch (NullPointerException e) {
            // let's not crash if the animation is blank
        }
    }



    // should be called by user
    public void setUID(short i) {
        UID = i;
    }






}
